﻿using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;

namespace Neitzel.Irc
{
    /// <summary>
    /// Stores universal Flags that are either a lower or uppercase char
    /// </summary>
    public class UniversalAttributes : INotifyPropertyChanged
    {
        #region Fields

        /// <summary>
        /// Tracesource for all log messages.
        /// </summary>
        private static readonly TraceSource Logger = new TraceSource(IrcConstants.TraceSourceName);

        /// <summary>
        /// Stores whether the flags are case sensitive or not.
        /// </summary>
        private bool _caseSensitive;

        /// <summary>
        /// Stores the current attributes.
        /// </summary>
        private string _attributes;

        #endregion

        #region Lifetime

        /// <summary>
        /// Initializes a new instance of the UniversalAttributes class with empty flags.
        /// </summary>
        public UniversalAttributes()
        {
            _caseSensitive = Properties.Settings.Default.FlagsCaseSensitive;
        }

        /// <summary>
        /// Initializes a new instance of the UniversalAttributes class with empty flags.
        /// </summary>
        /// <param name="caseSensitive">Defines whether the flags are case sensitive or not.</param>
        public UniversalAttributes(bool caseSensitive)
            : this()
        {
            _caseSensitive = caseSensitive;
        }
        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets a value indicating whether flags are case sensitive or not.
        /// </summary>
        /// <remarks>
        /// When CaseSensetive is set to false, the Flags are converted into Uppercase.
        /// </remarks>
        public bool CaseSensitive
        {
            get
            {
                return _caseSensitive;
            }

            set
            {
                _caseSensitive = value;

                // if we are no longer case Sensitive. Switch to Uppercase!
                if (!CaseSensitive && !String.IsNullOrEmpty(Attributes))
                    Attributes = Attributes.ToUpper(CultureInfo.InvariantCulture);

                OnPropertyChanged(this, new PropertyChangedEventArgs("CaseSensitive"));
             }
        }

        /// <summary>
        /// Gets or sets the flags.
        /// </summary>
        /// <remarks>
        /// The Property will never return null. default is empty string.
        /// </remarks>
        public string Attributes
        {
            get { return _attributes ?? String.Empty; }
            set { _attributes = value; OnPropertyChanged(this, new PropertyChangedEventArgs("Attributes"));}
        }

        #endregion

        #region Public Methods

        /// <summary>
        /// Modify the Flags regarding the change-String
        /// </summary>
        /// <remarks>
        /// The given String of changes to do can contain any characters with a leading + or -.
        /// The String will be scanned from left to right
        /// </remarks>
        /// <param name="changeCode">String like "+oV-x" that should be processed</param>
        public void Change(string changeCode)
        {
            // validate
            if (changeCode == null) throw new ArgumentNullException(nameof(changeCode));
            
            // remember old attributes to log changes afterwards.
            string oldAttribs = _attributes;

            bool add = true;
            foreach (char c in changeCode)
            {
                switch (c)
                {
                    case '+':
                        add = true;
                        break;

                    case '-':
                        add = false;
                        break;

                    default:
                        SetAttribute(c, add);
                        break;
                }
            }
            Logger.TraceEvent(TraceEventType.Verbose, (int)IrcTraceId.UniversalAttributes, Properties.Resources.UniversalAttributeChanged, oldAttribs, Attributes, changeCode);
        }

        /// <summary>
        /// Check if a Flag is set
        /// </summary>
        /// <param name="flag">Flag to check</param>
        /// <returns>True if the flag is set, else false.</returns>
        public bool IsSet(char flag)
        {
            // get a searchstring. If not CaseSensitive: in Uppercase!
            string search = CaseSensitive ? flag.ToString() : flag.ToString().ToUpper(CultureInfo.InvariantCulture);

            // Return if the Flag is inside stored Flags
            return Attributes.Contains(search);
        }

        /// <summary>
        /// Set a Flag
        /// </summary>
        /// <param name="flag">Flag to set</param>
        /// <param name="add">true if Flag should be added, false if it should be removed.</param>
        public void SetAttribute(char flag, bool add)
        {
            // get a searchstring. If not CaseSensitive: in Uppercase!
            string search = CaseSensitive ? flag.ToString() : flag.ToString().ToUpper(CultureInfo.InvariantCulture);

            if (add)
            {
                // Check if Flag is not already inside
                if (!Attributes.Contains(search))
                    Attributes = Attributes + search;
            }
            else
            {
                int loc;

                // While Flag is inside
                while ((loc = Attributes.IndexOf(search, StringComparison.Ordinal)) > -1)
                {
                    Attributes = Attributes.Substring(0, loc) + Attributes.Substring(loc + 1);
                }
            }
        }

        #endregion

        #region INotifyPropertyChanged parts

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            // prevent racing conditions
            var handler = PropertyChanged;

            // trigger event
            handler?.Invoke(sender, e);
        }

        #endregion
    }
}
